package filestorage;

import handlers.FileDeletionHandler;
import handlers.FileHandler;
import handlers.ReplicationHandler;
import handlers.StatusHandler;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Map;

import status.FileItemStatus;
import tcp.filetransfer.FileTransferClient;

import model.jena.ModelBuilder;
import model.jena.properties.FileStore;

import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.util.FileManager;

public class FileStorageManager implements FileHandler, StatusHandler, FileDeletionHandler, ReplicationHandler{

	public final String DEFAULT_DIRECTORY = "C:\\storage";
	private String location;
	private File storageDirectory;
	private ArrayList<FileStoreItem> listOfStoredItems ;

	public FileStorageManager() {
		if(this.location == null){
			storageDirectory = new File(DEFAULT_DIRECTORY) ;
		}else{
			storageDirectory = new File(this.location);
		}
		if(storageDirectory.exists() && storageDirectory.isDirectory()){
			File[] exsitingFilesInStorgae = storageDirectory.listFiles();
			this.listOfStoredItems = new ArrayList<FileStoreItem>();
			if(exsitingFilesInStorgae.length > 0 ){
				this.intialiseFileStorageFromFiles(exsitingFilesInStorgae);
			}
			
		}else{
			storageDirectory.mkdirs();
		}
	}
	public FileStorageManager(String location) {
		this.location = location;
		if(this.location == null){
			storageDirectory = new File(DEFAULT_DIRECTORY) ;
		}else{
			storageDirectory = new File(this.location);
		}
		if(storageDirectory.exists() && storageDirectory.isDirectory()){
			File[] exsitingFilesInStorgae = storageDirectory.listFiles();
			this.listOfStoredItems = new ArrayList<FileStoreItem>();
			if(exsitingFilesInStorgae.length > 0 ){
				this.intialiseFileStorageFromFiles(exsitingFilesInStorgae);
			}
			
		}else{
			storageDirectory.mkdirs();
		}
	}
	private int searchForFile(String filename){
		int foundFile = -1 ;
		for(int i = 0; i < listOfStoredItems.size(); i++){
			String storedFile = this.listOfStoredItems.get(i).getName();
			if(storedFile.equalsIgnoreCase(filename)){
				foundFile = i;
			}
		}
		return foundFile;
	}
	private void intialiseFileStorageFromFiles(File[] filesInStorage){
		for(int i = 0; i < filesInStorage.length; i++){
			//In each FileFolder there should be 3 files
			//1. The file
			//2. The MD5 hash
			//3. The status RDF file
			//If not a folder, not interested
			if(filesInStorage[i].exists() && filesInStorage[i].isDirectory()){
				File[] filesInFileFolder = filesInStorage[i].listFiles();
				if(filesInFileFolder.length == 2){
					FileStoreItem newItem = new FileStoreItem();
					for(int j = 0; j < filesInFileFolder.length; j++){
						boolean isMD5Hash = filesInFileFolder[j].getName().endsWith("chk") ;
						boolean isRDFFile = filesInFileFolder[j].getName().endsWith(".RDF") ;
						if(isMD5Hash){
							//Load MD5 hash into object
							//newItem.setHash(this.getMD5HashFromFile(filesInFileFolder[j]));
						}
						if(isRDFFile){
							//Load Status into file
							//TODO Load model from file
							newItem.setStatus(this.getStatusFromRDF(filesInFileFolder[j].getName()));
							//newItem.setItemStatusFile(filesInFileFolder[i]);
						}
						if(!isMD5Hash && !isRDFFile){
							//Load the file descriptor into the object
							newItem.setItemFile(filesInFileFolder[j]) ;
							newItem.setName(filesInFileFolder[j].getName());
							newItem.setLocation(filesInFileFolder[j].getAbsolutePath());
						}
						
					}
					if(newItem != null){
						listOfStoredItems.add(newItem);
					}
				}
			}
			
		}
	}
	private String getMD5HashFromFile(File md5File){
		BufferedReader reader = null;
		String hash = "" ;
		try {

			reader = new BufferedReader(new FileReader(md5File));
			hash = reader.readLine();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			try {
				reader.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return hash;
	}
	private FileItemStatus getStatusFromRDF(String rdfFileName){
        InputStream in = FileManager.get().open( rdfFileName );
        Model model = ModelFactory.createDefaultModel();
        if (in == null) {
            throw new IllegalArgumentException( "File: " + rdfFileName + " not found");
        }
        // read the RDF/XML file
        model.read(new InputStreamReader(in), "");
        Resource fileStatusResource = model.getResource(null) ;
        //TODO debug this section
        long fileSlowest = Long.parseLong(fileStatusResource.getProperty(FileStore.SLOWEST_SERVICE_TIME).getString());
        long fileQuickest = Long.parseLong(fileStatusResource.getProperty(FileStore.QUICKEST_SERVICE_TIME).getString());
        int fileTotal = Integer.parseInt(fileStatusResource.getProperty(FileStore.TOTAL_NUM_DOWNLOADS).getString());
        long fileAvg = Long.parseLong(fileStatusResource.getProperty(FileStore.AVG_SERVICE_TIME).getString());
        
        FileItemStatus stats = new FileItemStatus(fileTotal, fileAvg, fileQuickest, fileSlowest);
        
		return stats;
	}
	public boolean findFile(String filename) {
		return (this.searchForFile(filename) != -1?true:false);
	}

	public File getFile(String filename) {
		return this.listOfStoredItems.get(this.searchForFile(filename)).getFile();
	}
	public boolean createFile(String filename, boolean fileExsists,
			String[] data) {
		if(fileExsists){
			this.deleteFile(filename);
			this.createFilesFromUpload(filename,data);
		}else{
			this.createFilesFromUpload(filename, data);
		}
		return true;
	}
	private void createFilesFromUpload(String filename, String[] data){
		String directoryName = filename.replaceAll("\\.", "_");
		File directory = new File(storageDirectory.getAbsolutePath()+File.separator+directoryName);
		System.out.println(directory.getAbsolutePath());
		//Make sure it doesn't exsist, fecked if it is
		String hash = "";
		if(!directory.exists()){
			directory.mkdirs();
			File uploadDestination = new File(directory,filename);
			try {
				uploadDestination.createNewFile();
				PrintWriter fileOut = new PrintWriter(uploadDestination);
				for(int i = 0; i < data.length; i++){
					fileOut.print(data[i]);
				}
				fileOut.close();
				//Create MD5 Hash of the file
				this.createCheckSumFile(directory, filename);
				hash = new String(this.createChecksum(directory, filename));
				//Add file to the filestore to the listOfFiles stored
				this.addFileToManagedList(filename, uploadDestination.getAbsolutePath(),uploadDestination, hash);
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
	}
	private void addFileToManagedList(String filename,String location,String hash){
		FileStoreItem item = new FileStoreItem("",location,filename,new FileItemStatus());
		this.listOfStoredItems.add(item);
	}
	private void addFileToManagedList(String filename,String location,File uploadedFile,String hash){
		FileStoreItem item = new FileStoreItem("",location,filename,new FileItemStatus());
		item.setItemFile(uploadedFile);
		this.listOfStoredItems.add(item);
	}
	private void createCheckSumFile(File directory, String filename){
	     try {
	         byte[] chk = createChecksum(directory,filename);
	         filename = filename.replaceAll("\\.", "_");
	         File f = new File(directory,filename + ".chk");
	         OutputStream os = new FileOutputStream(f);
	         os.write(chk);
	         os.close();
	      }
	      catch(Exception e) {
	         e.printStackTrace();
	      }
	}
	private byte[] createChecksum(File directory,String filename) throws Exception{
	     InputStream fis =  new FileInputStream(new File(directory,filename));
	     byte[] buffer = new byte[1024];
	     MessageDigest complete = MessageDigest.getInstance("MD5");
	     int numRead;
	     do {
	       numRead = fis.read(buffer);
	       if (numRead > 0) {
	         complete.update(buffer, 0, numRead);
	       }
	     } while (numRead != -1);
	     fis.close();
	     return complete.digest();
	}
	public String getStatus() {
		String status = null ;
		if(this.listOfStoredItems != null){
			Model model = ModelBuilder.buildModel(this.listOfStoredItems);
			StringWriter stWritter = new StringWriter();
			model.write(System.out);
			model.write(stWritter);
			status = stWritter.toString();
		}
		return status;
	}
	public synchronized void deleteFile(String filename) {
		for(int i = 0; i < this.listOfStoredItems.size(); i++){
			if(listOfStoredItems.get(i).getName().equalsIgnoreCase(filename)){
				listOfStoredItems.remove(i);
				break;
			}
		}
		filename = filename.replaceAll("\\.", "_");
		File fileToDelete = new File(storageDirectory.getAbsolutePath()+File.separatorChar+filename);
		if(fileToDelete.exists() && fileToDelete.isDirectory()){
			File[] listOfFiles = fileToDelete.listFiles();
			if(listOfFiles != null && listOfFiles.length > 0){
				for(int i = 0; i < listOfFiles.length; i++){
					listOfFiles[i].delete();
				}
			}
			fileToDelete.delete();
		}
	}
	public Map<String, String> provideStats(String filename,long startTime, long endTime) {
		// TODO Auto-generated method stub
		long totalTime = endTime - startTime;
		for(int i = 0; i < listOfStoredItems.size(); i++){
			FileStoreItem item = listOfStoredItems.get(i);
			if(item.getName().equalsIgnoreCase(filename)){
				item.getStatus().incrementDownloadCount();
				item.getStatus().updateServiceTime(totalTime);
			}
		}
		return null;
	}
	private File getFileByName(String filename){
		File filefound = null;
		if(findFile(filename)){
			String folderName = filename.replaceAll("\\.", "_");
			filefound = new File(storageDirectory.getAbsolutePath()+File.separator+folderName+File.separator+filename);
		}
		return filefound;
	}
	public void handleReplicationRequest(String fileName, String destination,
			int destinationPort) {
		FileTransferClient.requestFileUpload(destination,destinationPort,this.getFileByName(fileName));
		
	}
	
	
}
